/*
 * SyncResource.java
 *
 * Created on April 12, 2007, 1:39 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.auth.service.app;

import java.sql.SQLException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import org.atomojo.app.client.XMLEntity;
import org.atomojo.auth.service.db.AuthDB;
import org.atomojo.auth.service.db.Group;
import org.atomojo.auth.service.db.Permission;
import org.atomojo.auth.service.db.Realm;
import org.atomojo.auth.service.db.RealmUser;
import org.atomojo.auth.service.db.Role;
import org.atomojo.auth.service.db.User;
import org.atomojo.auth.service.db.XML;
import org.restlet.Request;
import org.restlet.data.ChallengeRequest;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.ServerResource;

/**
 *
 * @author alex
 */
public class AuthResource extends ServerResource
{
   
   long expiration = 3600*1000;
   AuthDB db;
   Realm realm;
   boolean realmRequired;
   /** Creates a new instance of SyncResource */
   public AuthResource() {
      setNegotiated(false);
   }

   protected void doInit() {
      db = (AuthDB)getRequest().getAttributes().get(AuthApplication.DB_ATTR);
      realm = (Realm)getRequest().getAttributes().get(AuthApplication.REALM_ATTR);
      Boolean checkRealm = (Boolean)getRequest().getAttributes().get(AuthApplication.REALM_REQUIRED_ATTR);
      realmRequired = checkRealm==null ? false : checkRealm.booleanValue();
   }
   
   static User findUser(AuthDB db,String authid)
      throws SQLException,IllegalArgumentException
   {
      User user = null;
      if (authid.startsWith("urn:uuid:")) {
         // by uuid
         UUID userid = UUID.fromString(authid.substring(9));
         user = db.getUser(userid);
      } else if (authid.indexOf('@')>0) {
         // by e-mail
         user = db.findUserByEmail(authid);
      } else {
         // by alias
         user = db.getUser(authid);
      }
      return user;
   }
   
   static RealmUser findRealmUser(AuthDB db,Realm realm,String authid)
      throws SQLException,IllegalArgumentException
   {
      RealmUser user = null;
      if (authid.startsWith("urn:uuid:")) {
         // by uuid
         UUID userid = UUID.fromString(authid.substring(9));
         user = db.getRealmUser(realm,userid);
      } else if (authid.indexOf('@')>0) {
         // by e-mail
         user = db.findRealmUserByEmail(realm,authid);
      } else {
         // by alias
         user = db.getRealmUser(realm,authid);
      }
      return user;
   }
   Representation getSessionEntity(UUID session,UUID userId,String userAlias,String name,String email,Iterator<Role> roles,Iterator<Group> groups)
   {
      Set<Role> userRoles = new HashSet<Role>();
      if (roles!=null) {
         while (roles.hasNext()) {
            userRoles.add(roles.next());
         }
      }
      StringBuilder groupsRep = new StringBuilder();
      if (groups!=null) {
         while (groups.hasNext()) {
            Group group = groups.next();
            groupsRep.append("<group id='"+group.getUUID()+"' alias='"+group.getAlias()+"'/>\n");
            Iterator<Role> groupRoles = group.getRoles();
            while (groupRoles!=null && groupRoles.hasNext()) {
               userRoles.add(groupRoles.next());
            }
         }
      }
      StringBuilder rolesRep = new StringBuilder();
      for (Role role : userRoles) {
         rolesRep.append("<role id='"+role.getUUID()+"' name='"+role.getName()+"'/>\n");
      }
      return new StringRepresentation(
          "<session xmlns='"+XML.authNS+"' "+(session==null ? "" : "id='"+session+"' ")+"user-id='"+userId+"'"+(userAlias==null ? "" : " user-alias='"+userAlias+"'")+">\n" +
          (name==null ? "" : "<name>"+XMLEntity.escape(name)+"</name>\n") +
          (email==null ? "" : "<email>"+XMLEntity.escape(email)+"</email>\n") +
          groupsRep.toString() +
          rolesRep.toString() +
          "</session>",
          MediaType.APPLICATION_XML
      );
   }
   
   public Representation get()
   {
      if (realmRequired && realm==null) {
         getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
         return null;
      }
      Form requestForm = getRequest().getResourceRef().getQueryAsForm();
      //getContext().getLogger().info("Testing auth...");
      try {
         ChallengeResponse authorization = getRequest().getChallengeResponse();
         String idValue = AuthApplication.getStringAttribute(getRequest(),"session",null);
         if (authorization==null || idValue!=null) {
            if (idValue!=null) {
               try {
                  UUID id = UUID.fromString(idValue);
                  User.Authenticated authd = db.isAuthenticated(realm,id);
                  if (authd!=null) {
                     String userAlias = authd.getUser().getAlias();
                     UUID userId = authd.getUser().getUUID();
                     String name = authd.getUser().getName();
                     String email = authd.getUser().getEmail();
                     Iterator<Group> groups = null;
                     Iterator<Role> roles = authd.getUser().getRoles();
                     // If we have a realm, we need the realm user to get the right alias
                     if (realm!=null) {
                        RealmUser ruser = db.findRealmUser(realm,authd.getUser());
                        if (ruser!=null) {
                           userAlias = ruser.getAlias();
                           name = ruser.getName();
                           email = ruser.getEmail();
                           groups = ruser.getGroups();
                        }
                     }
                     getResponse().setStatus(Status.SUCCESS_OK);
                     return getSessionEntity(authd.getSession(),userId,userAlias,name,email,roles,groups);
                  }
               } catch (IllegalArgumentException ex) {
                  getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
                  return null;
               }
            }
            getResponse().setChallengeRequests(Collections.singletonList(new ChallengeRequest(ChallengeScheme.HTTP_BASIC,realm==null ? "users" : "realm "+realm.getName())));
            getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
            return null;
         }
         String authid = authorization.getIdentifier().trim();
         String password = new String(authorization.getSecret());
         User user = null;
         if (realm!=null) {
            RealmUser ruser = findRealmUser(db,realm,authid);
            if (ruser!=null) {
               user = ruser.getUser();
            }
            if (user==null) {
               // see if the user is a super user across realms
               user = findUser(db,authid);
               if (user!=null) {
                  // The user must either be a superuser or have the cross-realm permission
                  Permission superuser = db.getPermission(AuthDB.SUPERUSER_PERMISSION);
                  Permission crossrealm = db.getPermission(AuthDB.ACROSS_REALM_PERMISSION);
                  if (!user.hasPermission(superuser) && !user.hasPermission(crossrealm)) {
                     user = null;
                  }
               }
            }
         } else {
            user = findUser(db,authid);
         }
         if (user!=null) {
            try {
               String seconds = requestForm.getFirstValue("expiration");
               long requestExpiration = seconds==null ? expiration : Long.parseLong(seconds)*1000;
               if ("false".equals(requestForm.getFirstValue("session"))) {
                  requestExpiration = 0;
               }
               User.Authenticated authd = user.authenticate(realm,password,requestExpiration);
               if (authd!=null) {
                  String userAlias = authd.getUser().getAlias();
                  UUID userId = authd.getUser().getUUID();
                  String name = authd.getUser().getName();
                  String email = authd.getUser().getEmail();
                  Iterator<Group> groups = null;
                  Iterator<Role> roles = authd.getUser().getRoles();
                  // If we have a realm, we need the realm user to get the right alias
                  if (realm!=null) {
                     RealmUser ruser = db.findRealmUser(realm,authd.getUser());
                     if (ruser!=null) {
                        userAlias = ruser.getAlias();
                        name = ruser.getName();
                        email = ruser.getEmail();
                        groups = ruser.getGroups();
                     }
                  }
                  getResponse().setStatus(Status.SUCCESS_OK);
                  return getSessionEntity(authd.getSession(),userId,userAlias,name,email,roles,groups);
               }
            } catch (Exception ex) {
               getContext().getLogger().log(Level.SEVERE,"Failed to authenticate due to exception.",ex);
               getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
               return null;
            }
         }
         getResponse().setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
         getResponse().setChallengeRequests(Collections.singletonList(new ChallengeRequest(ChallengeScheme.HTTP_BASIC,realm==null ? "Realm Users" : "Users")));
      } catch (SQLException ex) {
         getContext().getLogger().log(Level.SEVERE,"Cannot get user data from database.",ex);
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
      }
      return null;
   }
   
   public boolean allowDelete() {
      return true;
   }
   
   public Representation delete() {
      String sessionS = AuthApplication.getStringAttribute(getRequest(),"session",null);
      if (sessionS==null) {
         getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
         return null;
      }
      try {
         UUID session = UUID.fromString(sessionS);
         db.expireSession(session);
      } catch (IllegalArgumentException ex) {
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
      } catch (SQLException ex) {
         getContext().getLogger().log(Level.SEVERE,"Cannot delete session "+sessionS+" from database.",ex);
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
      }
      return null;
   }
   
  
}
